import sys
from types import StringType, UnicodeType
from random import randint
from hashlib import md5
from urlparse import urlsplit
from mediadart import log
from mediadart.storage import new_id
from mediadart.mqueue.mqclient_twisted import ServerProxy
from twisted.internet import reactor, defer
from twisted.python.failure import Failure
from twisted.web.client import  HTTPClientFactory, HTTPPageGetter


class Uploader(HTTPClientFactory):
    protocol = HTTPPageGetter
    def __init__(self, url, data, headers, followRedirect=False):
        log.debug('Uploader.__init__(url=%s headers=%s)' % (url, headers))
        HTTPClientFactory.__init__(self, url=str(url), method='POST', postdata=data,
               headers=headers, followRedirect=followRedirect)

    def gotStatus(self, version, status, message):
        if status == '200':
            self.deferred.callback(status)
        else:
            self.deferred.errback(Exception('%s: %s' % (status, message)))

    def noPage(self, reason):
        pass

    def page(self, page):
        #log.debug('-'*80)
        #log.debug(page)
        return

class FakeUploader:
    def __init__(self, deferred, url, data, headers, followRedirect=False):
        #log.debug('fake: starting', headers['chunk_number'])
        if randint(0, 3) < 2:
            #log.debug('fake: success', headers['chunk_number'])
            #reactor.callLater(0.0, deferred.callback, 'ok')
            deferred.callback('ok')
        else:
            #log.debug('fake: failure', headers['chunk_number'])
            #reactor.callLater(0.0, deferred.errback,Failure(Exception('fallito')))
            deferred.errback(Failure(Exception('fallito')))
        

class UploadFile(object):
    max_outstanding = 3
    chunksize = 512*1024
    max_failures = 3
    stopped = 0
    def __init__(self, filename, url, res_id):
        self.f =  open(filename, 'r')
        self.f.seek(0, 2)
        self.filesize = self.f.tell()
        #log.debug('filesize = %d' % self.filesize)
        self.res_id = res_id
        self.url = url
        u = urlsplit(url)
        self.host = u.hostname
        self.port = u.port
        self.todo = range(0, self.filesize, self.chunksize)
        self.total_chunks = len(self.todo)
        self.done = []
        self.in_process = []
        self.failed=0

    def run(self):
        self.deferred = defer.Deferred()
        for pos in self.todo[:self.max_outstanding]:
            #log.debug('starting', pos)
            self._send_chunk(pos)
        return self.deferred
        #log.debug('END OF RUN')

    def _get_chunk(self, pos):
        self.f.seek(pos)
        chunk = []
        length = min(self.chunksize, (self.filesize - pos))
        left = length
        while left:
            s = self.f.read(left)
            if not len(s):
                raise UploadException('unexpected end of file')
            left -= len(s)
            chunk.append(s)
        log.debug('joining chunk')
        chunk = ''.join(chunk)
        log.debug('computing md5')
        hash = md5(chunk).hexdigest()
        log.debug('returnring values, hash = %s' % hash)
        return chunk, hash

    def _send_chunk(self, pos):
        log.debug('_send_chunk %d' % pos)
        if pos not in self.todo or pos in self.in_process:
            log.debug('_send_chunk: nothing to do')
            return
        self.todo.remove(pos)
        self.in_process.append(pos)
        chunk, hash = self._get_chunk(pos)
        log.debug('starting uploader')
        uploader = Uploader(self.url, chunk, 
                     {'total_chunks': self.total_chunks, 'res_id': self.res_id, 
                     'chunk_number':pos/self.chunksize, 'md5':hash,
                     'content-type':'application/octect-stream'})
        uploader.deferred.addBoth(self._get_response, pos)
        reactor.connectTCP(self.host, self.port, uploader)
        #log.debug('_send_chunk todo', self.todo)
        #log.debug('_send_chunk in_p', self.in_process)
        #log.debug('_send_chunk done', self.done)
        #log.debug('_send_chunk EXIT', pos)

    def _get_response(self, response, pos):
        log.debug('_get_response: %d' % pos)
        if type(response) in [StringType, UnicodeType]:
            log.debug('OK: %d' % pos)
            self.failed = 0
            if pos in self.in_process:
                self.in_process.remove(pos)
            if pos not in self.done:
                self.done.append(pos)
            #log.debug('_get_response todo', self.todo)
            #log.debug('_get_response in_p', self.in_process)
            #log.debug('_get_response done', self.done)
        elif isinstance(response, Failure):
            log.debug('FAIL: %d' % pos)
            #log.debug(response)
            self.failed += 1
            if pos in self.in_process:
                self.in_process.remove(pos)
            if pos not in self.todo:
                self.todo.append(pos)
            #log.debug('_get_response todo', self.todo)
            #log.debug('_get_response in_p', self.in_process)
            #log.debug('_get_response done', self.done)
        else:
            self.failed += 1
            log.error('ERROR: unrecognized response type %s' % response)

        if self.failed > self.max_failures:
            log.error('MAX number of failures reached')
            self.signal_complete()
        elif self.todo:
            if len(self.in_process) < self.max_outstanding:
                #log.debug('_get_response: launching', self.todo[0])
                self._send_chunk(self.todo[0])
        else:
            if not self.in_process:
                self.signal_complete()

    def signal_complete(self):
        if self.stopped:
            return
        self.stopped = 1
        if len(self.done) == self.total_chunks:
            self.deferred.callback(self.res_id)
        else:
            self.deferred.errback(Exception('not transerred %d chunks' % 
                         (self.total_chunks-len(self.done))))

def shutdown2(res_id):
    log.info('**** SHUTDOWN2 ****')
    log.debug('_shutdown %s' % res_id)
    code = 0
    if isinstance(res_id, Failure):
        log.error('ERROR: %s: exit code=1' % Failure)
        code = 1
    else:
        log.error('exit code=0')
    reactor.stop()

def _shutdown(res_id):
    log.info('SHUTDOWN')
    log.debug('_shutdown %s' % res_id)
    code = 0
    if isinstance(res_id, Failure):
        log.error('ERROR: %s: exit code=1' % Failure)
        code = 1
    else:
        log.error('exit code=0')
    reactor.stop()

def _finalize_add(res_id):
    log.debug('_finalize_add')
    try:
        s = ServerProxy('Storage')
        ret = s.add_resource(res_id)
    except Exception, e:
        _shutdown(e)
    ret.addBoth(_shutdown)


###  TOOLKIT interfaces ###
def upload_resource(url, filename, res_id, endcallback):
    log.debug('upload_resource %s %s %s' % (url, filename, res_id))
    uploader = UploadFile(filename, url, res_id)
    d = uploader.run()
    d.addBoth(endcallback)

def add_resource(filename, res_id):
    if not res_id:
        res_id = new_id(filename)
    log.debug('add_resource %s id=%s' % (filename, res_id))
    try:
        print 'debug=%s' % log.debug
        s = ServerProxy('Storage')
        log.debug('ServerProxy = %s' % s)
        d = s.get_upload_url()
    except Exception, e:
        print 'ERROR: %s' % str(e)
        _shutdown(e)
    log.debug('add_resource: adding to %s'  % d)
    d.addCallbacks(upload_resource, _shutdown, callbackArgs = [filename, res_id, _finalize_add],)

if __name__=='__main__':
    #upload_resource('http://localhost:8081/uploader', sys.argv[1], '11110123443210cba', _shutdown)
    add_resource(sys.argv[1], None)

#    chunk = open('__init__.pyc', 'r').read()
#    url = 'http://localhost:8081/uploader'
#    total_chunks = 1
#    res_id = 'abcdabcdabcd'
#    chunk_number = 1
#    hash = md5(chunk).hexdigest()
#    uploader = Uploader(url, chunk, 
#                 {'total_chunks': total_chunks, 'res_id': res_id, 
#                 'chunk_number':chunk_number, 'md5':hash,
#                 'content-type':'application/octect-stream'})
#    uploader.deferred.addBoth(_shutdown)
#    reactor.connectTCP('localhost', 8081, uploader)
    reactor.run()
